<?php

namespace yunj\core\builder;

use yunj\core\Config;
use yunj\core\constants\BuilderConst;
use yunj\core\enum\Def;
use yunj\core\Validate;
use yunj\core\response\Json;
use yunj\core\enum\BuilderType;
use yunj\core\control\YunjControl;
use yunj\core\exception\GeneralException;
use yunj\core\validate\YunjTableValidate;

final class YunjTable extends Builder {

    protected $type = BuilderType::TABLE;

    protected $builderValidateClass = YunjTableValidate::class;

    protected $scriptFileList = ["/static/yunj/js/table.min.js?v=" . YUNJ_VERSION];

    protected $options = ["state", "page", "pk", "limit", "limits", "filter"
        , "validate", "toolbar", "defaultToolbar", "tree", "cols", "count", "items", "event"];

    // 导出限制，每次2000条
    const EXPORT_LIMIT = 2000;

    /**
     * 状态栏（默认选中第一项）
     * 'state'=>[
     *      ['key'=>11,'title'=>'正常'],
     *      ['key'=>22,'title'=>'回收站'],
     *  ]
     * @var array
     */
    private $state = [];

    /**
     * 是否开启分页（默认true）
     * 示例：state没设置时 bool
     * "page"=>true
     * 示例：state设置时 array<state-key,bool>
     * "page"=>[
     *      11=>true,   // key为状态栏key值
     *      22=>false
     * ]
     * @var bool|array<state-key,bool>
     */
    private $page = true;

    /**
     * 主键表头key（默认id）
     * 示例：pk没设置时 为id
     * "pk"=>id
     * 示例：pk设置时 array<state-key,string>
     * "pk"=>[
     *      11=>'id_1',   // key为状态栏key值
     *      22=>'id_2'
     * ]
     * @var bool|array<state-key,string>
     */
    private $pk = 'id';

    /**
     * 每页显示数据条数（默认20）。在page开启时有效
     * 示例：state没设置时 int
     * "limit"=>20
     * 示例：state设置时 array<state-key,int>
     * "limit"=>[
     *      11=>20,     // key为状态栏key值
     *      22=>50
     * ]
     * @var int|array<state-key,int>
     */
    private $limit = Def::LIST_PAGE_LIMIT;

    /**
     * 每页条数的选择项（默认[10,20,30,40,50,60,70,80,90]）。在page开启时有效
     * 示例：state没设置时 array<int>
     * "limits"=>[10,20,30,40,50,60,70,80,90]
     * 示例：state设置时 array<state-key,array<int>>
     * 'limits'=>[
     *      11=>[10,30,50,70,90],   // key为状态栏key值
     *      22=>[20,40,60,80,90]
     *  ]
     * @var array<int>|array<state-key,array<int>>
     */
    private $limits = [10, 20, 30, 40, 50, 60, 70, 80, 90];

    /**
     * 筛选表单（默认空）
     * 示例：state没设置时 array<field-key,field-args>
     * 'filter'=>[
     *      'field_key'=>[
     *          'type'=>'text',         //（可选，默认text）
     *          'title'=>'field_label',
     *          'placeholder'=>'',      //（可选）
     *          'value'=>'',            //默认值（可选）
     *      ],...
     *  ]
     * 示例：state设置时 array<state-key,array<field-key,field-args>>
     * 'filter'=>[
     *      11=>[],
     *      22=>[
     *          'field_key'=>[
     *              'type'=>'text',         //（可选，默认text）
     *              'title'=>'field_label',
     *              'placeholder'=>'',      //（可选）
     *              'value'=>'',            //默认值（可选）
     *          ],...
     *      ]
     *  ]
     * @var array<field-key,field-args>|array<state-key,array<field-key,field-args>>
     */
    private $filter;

    /**
     * 头部工具栏（默认空）
     * 示例：state没设置时 array<event-key,event-args>
     * 'toolbar'=>[
     *      "add"=>[
     *          'type'=>'openPopup',
     *          'title'=>'添加',
     *          'icon'=>'layui-icon layui-icon-add-circle',
     *          'url'=>url('add')
     *      ],
     *      11=>[
     *          'type'=>'asyncEvent',
     *          'title'=>'还原',
     *          'dropdown'=>true
     *      ],
     *      33=>[
     *          'type'=>'asyncEvent',
     *          'title'=>'永久删除',
     *          'dropdown'=>true
     *      ],...
     * ]
     *
     * 示例：state设置时 array<state-key,array<event-key,event-args>>
     * 'toolbar'=>[
     *      11=>[],
     *      22=>[
     *          "add"=>[
     *              'type'=>'openPopup',
     *              'title'=>'添加',
     *              'icon'=>'layui-icon layui-icon-add-circle',
     *              'url'=>url('add')
     *          ],
     *          11=>[
     *              'type'=>'asyncEvent',
     *              'title'=>'还原',
     *              'dropdown'=>true
     *          ],
     *          33=>[
     *              'type'=>'asyncEvent',
     *              'title'=>'永久删除',
     *              'dropdown'=>true
     *          ],
     *          ...
     *      ]
     * ]
     * @var array<event-key,event-args>|array<state-key,array<event-key,event-args>>
     */
    private $toolbar;

    /**
     * 头部右侧工具栏（默认空）
     * 可选值有：filter 筛选列的显示隐藏、export 数据导出、print 打印当前显示的表格数据
     * 示例：state没设置时 array<string>
     * 'defaultToolbar'=>['filter','print','export'=>['auth'=>'data_export']]
     *
     * 示例：state设置时 array<state-key,array<string>>
     * 'defaultToolbar'=>[
     *      11=>[],
     *      22=>[
     *          'export'=>[
     *              'type'=>'',
     *              'title'=>'',
     *              'class'=>'',
     *              'url'=>'',
     *              'confirmText'=>'',
     *              'auth'=>'data_export',
     *          ],...
     *      ]
     * ]
     * @var array<string>|array<state-key,array<string>>
     */
    private $defaultToolbar;

    /**
     * 树形配置（默认false）
     * 示例：state没设置时 bool
     * "tree"=>true
     * 示例：state设置时 array<state-key,bool>
     * "tree"=>[
     *      11=>true,   // key为状态栏key值
     *      22=>false
     * ]
     * @var bool|array<state-key,bool>
     */
    private $tree = false;

    /**
     * 必须：表头
     * 示例：state没设置时 array<col-key,col-args>
     * 'cols'=>[
     *      'id'=>['type'=>'checkbox'],
     *      'name_cn'=>['title'=>'中文姓名'],
     *      ...
     * ]
     *
     * 示例：state设置时 array<state-key,array<col-key,col-args>>
     * 'cols'=>[
     *      11=>[],
     *      22=>[
     *          'id'=>['type'=>'checkbox'],
     *          'name_cn'=>['title'=>'中文姓名'],
     *          ...
     *      ]
     * ]
     * @var array<col-key,col-args>|array<state-key,array<col-key,col-args>>
     */
    private $cols;

    /**
     * 获取数量回调方法
     * @var callable
     */
    private $countCallback;

    /**
     * 获取数据项回调方法
     * @var callable
     */
    private $itemsCallback;

    /**
     * 事件触发回调方法
     * @var callable
     */
    private $eventCallback;

    protected function setAttr(array $args = []): void {
        parent::setAttr($args);
        $this->config = Config::get('table.', []);
    }

    /**
     * Notes: 状态栏
     * Author: Uncle-L
     * Date: 2020/5/12
     * Time: 16:20
     * @param callable|array|string|number $key
     * callable:
     *      function(){
     *          return [11=>"正常",22=>"回收站"];
     *      }
     * array:
     *      [11=>"正常",22=>"回收站"];
     * @param string $title
     * @return YunjTable
     */
    public function state($key, string $title = '') {
        if ($this->existError()) return $this;
        if (!is_callable($key)) {
            $callable = function () use ($key, $title) {
                return is_array($key) ? $key : [['key' => $key, 'title' => $title]];
            };
        } else {
            $callable = $key;
        }
        $this->setOptionCallbale('state', $callable);
        return $this;
    }

    /**
     * Notes: 执行状态栏回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:42
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_state_callable(callable $callable) {
        $state = $this->state ? $this->state : [];
        $result = call_user_func_array($callable, [$this]);
        if (!is_array($result)) return $this->error("YunjTable [state] 需返回有效数组");
        $this->state = array_merge($state, $result);
        return $this;
    }

    /**
     * Notes: 判断是否设置state
     * Author: Uncle-L
     * Date: 2022/3/8
     * Time: 17:30
     * @return bool
     */
    private function isSetState(): bool {
        $this->execOptionsCallbale("state");
        return $this->state && count($this->state) > 0;
    }

    /**
     * Notes: 分页开启
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:54
     * @param callable|bool $page
     * callable:
     *      function($state){
     *          return true;
     *      }
     * bool:
     *      true
     * @return YunjTable
     */
    public function page($page) {
        if ($this->existError()) return $this;
        if (!is_callable($page)) {
            $callable = function () use ($page) {
                return $page;
            };
        } else {
            $callable = $page;
        }
        $this->setOptionCallbale('page', $callable);
        return $this;
    }

    /**
     * Notes: 执行分页开启回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_page_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        if ($states) {
            $page = $this->page && is_array($this->page) ? $this->page : [];
            foreach ($states as $state) {
                $statePage = $callable($this, $state['key']);
                if (!is_bool($statePage)) return $this->error("YunjTable [page] 需返回布尔结果");
                $page[$state['key']] = $statePage;
            }
        } else {
            $page = $callable($this, null);
            if (!is_bool($page)) return $this->error("YunjTable [page] 需返回布尔结果");
        }
        $this->page = $page;
        return $this;
    }

    /**
     * 主键表头key
     * @param callable|string $pk
     * callable:
     *      function($state){
     *          return 'id';
     *      }
     * string:
     *      'id'
     * @return YunjTable
     */
    public function pk($pk) {
        if ($this->existError()) return $this;
        if (!is_callable($pk)) {
            $callable = function () use ($pk) {
                return $pk;
            };
        } else {
            $callable = $pk;
        }
        $this->setOptionCallbale('pk', $callable);
        return $this;
    }

    /**
     * Notes: 执行主键表头key回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_pk_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        if ($states) {
            $pk = $this->pk && is_array($this->pk) ? $this->pk : [];
            foreach ($states as $state) {
                $statePk = $callable($this, $state['key']);
                if (!is_string($statePk)) return $this->error("YunjTable [pk] 需返回字符串结果");
                $pk[$state['key']] = $statePk;
            }
        } else {
            $pk = $callable($this, null);
            if (!is_string($pk)) return $this->error("YunjTable [pk] 需返回字符串结果");
        }
        $this->pk = $pk;
        return $this;
    }

    /**
     * Notes: 每页显示条数
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:55
     * @param callable|int $limit
     * callable:
     *      function($state){
     *          return 20;
     *      }
     * int:
     *      20
     * @return YunjTable
     */
    public function limit($limit) {
        if ($this->existError()) return $this;
        if (!is_callable($limit)) {
            $callable = function () use ($limit) {
                return $limit;
            };
        } else {
            $callable = $limit;
        }
        $this->setOptionCallbale('limit', $callable);
        return $this;
    }

    /**
     * Notes: 执行每页显示条数回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_limit_callable(callable $callable) {
        if ($this->existError()) return $this;
        if (!$this->page) return $this;
        $states = $this->state;
        if ($states) {
            $limit = $this->limit && is_array($this->limit) ? $this->limit : [];
            foreach ($states as $state) {
                $stateLimit = $callable($this, $state['key']);
                if (!is_positive_int($stateLimit)) return $this->error("YunjTable [limit] 需返回正整数结果");
                $limit[$state['key']] = $stateLimit;
            }
        } else {
            $limit = $callable($this, null);
            if (!is_positive_int($limit)) return $this->error("YunjTable [limit] 需返回正整数结果");
        }
        $this->limit = $limit;
        return $this;
    }

    /**
     * Notes: 每页显示条数的选择项
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:55
     * @param callable|array<int>|...int $limits
     * callable:
     *      function($state){
     *          return [10,20,30];
     *      }
     * array:
     *      [10,20,30]
     * @return YunjTable
     */
    public function limits(...$limits) {
        if ($this->existError()) return $this;
        if (!$limits) return $this;
        $limit0 = $limits[0];
        if (is_callable($limit0)) {
            $callable = $limit0;
        } elseif (is_array($limit0)) {
            $callable = function () use ($limit0) {
                return $limit0;
            };
        } else {
            $callable = function () use ($limits) {
                return $limits;
            };
        }
        $this->setOptionCallbale('limits', $callable);
        return $this;
    }

    /**
     * Notes: 执行每页显示条数的选择项回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_limits_callable(callable $callable) {
        if ($this->existError()) return $this;
        if (!$this->page) return $this;
        $states = $this->state;
        if ($states) {
            $limits = $this->limits ?: [];
            foreach ($this->state as $state) {
                $stateLimits = $callable($this, $state['key']);
                if (!is_array($stateLimits)) return $this->error("YunjTable [limits] 需返回正整数数组");
                $limits[$state['key']] = $stateLimits;
            }
        } else {
            $limits = $callable($this, null);
            if (!is_array($limits)) return $this->error("YunjTable [limits] 需返回正整数数组");
        }
        $this->limits = $limits;
        return $this;
    }

    /**
     * Notes: 筛选表单
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:58
     * @param callable|array<field-key,field-args> $filter
     * @return YunjTable
     */
    public function filter($filter) {
        if ($this->existError()) return $this;
        if (!is_callable($filter)) {
            $callable = function () use ($filter) {
                return $filter;
            };
        } else {
            $callable = $filter;
        }
        $this->setOptionCallbale('filter', $callable);
        return $this;
    }

    /**
     * Notes: 执行筛选表单回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_filter_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        $filter = $this->filter ?: [];
        if ($states) {
            foreach ($this->state as $state) {
                $stateFilter = $callable($this, $state['key']);
                if (!is_array($stateFilter)) return $this->error("YunjTable [filter] 需返回有效数组");
                $filter[$state['key']] = $filter[$state['key']] ?? [];
                $this->handleFilterState($filter[$state['key']], $stateFilter, $state['key']);
            }
        } else {
            $_filter = $callable($this, null);
            if (!is_array($_filter)) return $this->error("YunjTable [filter] 需返回有效数组");
            $this->handleFilterState($filter, $_filter);
        }
        $this->filter = $filter;
        return $this;
    }

    /**
     * Notes: 判断是否设置filter
     * Author: Uncle-L
     * Date: 2022/3/8
     * Time: 17:30
     * @return bool
     */
    private function isSetFilter(): bool {
        $this->execOptionsCallbale("filter");
        return !!$this->filter;
    }


    /**
     * Notes: 判断是否设置tree
     * Author: Uncle-L
     * Date: 2022/3/8
     * Time: 17:30
     * @param null|mixed $state
     * @return bool
     */
    private function isSetTree($state = null): bool {
        $this->execOptionsCallbale("state,tree");
        return ($state !== null && isset($this->tree[$state]) && $this->tree[$state] === true) || ($state === null && $this->tree === true);
    }

    /**
     * Notes: 处理状态栏对应的筛选表单字段
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:59
     * @param array $filterState
     * @param array $fields
     * @return YunjTable
     */
    protected function handleFilterState(array &$filterState, array $fields, ?string $state = null) {
        $filterFormId = $this->id . ($state ? "_{$state}" : '');
        foreach ($fields as $key => $field) {
            if (!$field || !is_array($field)) return $this->error("YunjTable [filter] 返回参数[{$key}]异常");
            list($error, $args) = YunjControl::formField($field, $filterFormId);
            if ($error) return $this->error($error);
            if ($args) $filterState[$key] = $args;
        }
        return $this;
    }

    /**
     * Notes: 头部工具栏
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:00
     * @param callable|array<event-key,event-args> $toolbar
     * @return YunjTable
     */
    public function toolbar($toolbar) {
        if ($this->existError()) return $this;
        if (!is_callable($toolbar)) {
            $callable = function () use ($toolbar) {
                return $toolbar;
            };
        } else {
            $callable = $toolbar;
        }
        $this->setOptionCallbale('toolbar', $callable);
        return $this;
    }

    /**
     * Notes: 执行头部工具栏回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_toolbar_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        $toolbar = $this->toolbar ? $this->toolbar : [];
        if ($states) {
            foreach ($states as $state) {
                $stateToolbar = $callable($this, $state['key']);
                if (!is_array($stateToolbar)) return $this->error("YunjTable [toolbar] 需返回有效数组");
                $toolbar[$state['key']] = $toolbar[$state['key']] ?? [];
                foreach ($stateToolbar as $event => $args) {
                    list($error, $args) = YunjControl::tableToolbar($args);
                    if ($error) return $this->error($error);
                    if ($args) $toolbar[$state['key']][$event] = $args;
                }
            }
        } else {
            $_toobar = $callable($this, null);
            if (!is_array($_toobar)) return $this->error("YunjTable [toolbar] 需返回有效数组");
            foreach ($_toobar as $event => $args) {
                list($error, $args) = YunjControl::tableToolbar($args);
                if ($error) return $this->error($error);
                if ($args) $toolbar[$event] = $args;
            }
        }
        $this->toolbar = $toolbar;
        return $this;
    }

    /**
     * Notes: 头部右侧工具栏
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:02
     * @param callable|array<string>|...string $defaultToolbar
     * callable:
     *      function($state){
     *          return ["filter","print"];
     *      }
     * array<string>:
     *      ["filter","print"]
     * ...string:
     *      "filter","print"
     * @return YunjTable
     */
    public function defaultToolbar(...$defaultToolbar) {
        if ($this->existError()) return $this;

        if (!$defaultToolbar) return $this;
        $defaultToolbar0 = $defaultToolbar[0];
        if (is_callable($defaultToolbar0)) {
            $callable = $defaultToolbar0;
        } elseif (is_array($defaultToolbar0)) {
            $callable = function () use ($defaultToolbar0) {
                return $defaultToolbar0;
            };
        } else {
            $callable = function () use ($defaultToolbar) {
                return $defaultToolbar;
            };
        }
        $this->setOptionCallbale('defaultToolbar', $callable);
        return $this;
    }

    /**
     * Notes: 执行头部右侧工具栏回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_defaultToolbar_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        $defaultToolbar = $this->defaultToolbar ?: [];
        if ($states) {
            foreach ($states as $state) {
                $stateDefaultToolbar = $callable($this, $state['key']);
                if (!is_array($stateDefaultToolbar)) return $this->error("YunjTable [defaultToolbar] 需返回有效数组");
                $defaultToolbar[$state['key']] = $defaultToolbar[$state['key']] ?? [];
                foreach ($stateDefaultToolbar as $k => $v) {
                    list($error, $key, $args) = YunjControl::tableDefaultToolbar($k, $v);
                    if ($error) return $this->error($error);
                    if ($args) {
                        $defaultToolbar[$state['key']][$key] = $args;
                    }
                }
            }
        } else {
            $_defaultToolbar = $callable($this, null);
            if (!is_array($_defaultToolbar)) return $this->error("YunjTable [defaultToolbar] 需返回有效数组");
            foreach ($_defaultToolbar as $k => $v) {
                list($error, $key, $args) = YunjControl::tableDefaultToolbar($k, $v);
                if ($error) return $this->error($error);
                if ($args) {
                    $defaultToolbar[$key] = $args;
                }
            }
        }
        $this->defaultToolbar = $defaultToolbar;
        return $this;
    }

    /**
     * Notes: 树形
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:00
     * @param callable|bool $tree
     * callable:
     *      function($state){
     *          return true;
     *      }
     * bool:
     *      true
     * @return YunjTable
     */
    public function tree($tree) {
        if ($this->existError()) return $this;
        if (!is_callable($tree)) {
            $callable = function () use ($tree) {
                return $tree;
            };
        } else {
            $callable = $tree;
        }
        $this->setOptionCallbale('tree', $callable);
        return $this;
    }

    /**
     * Notes: 执行树形回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_tree_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        if ($states) {
            $tree = $this->tree && is_array($this->tree) ? $this->tree : [];
            foreach ($states as $state) {
                $statePage = $callable($this, $state['key']);
                if (!is_bool($statePage)) return $this->error("YunjTable [tree] 需返回布尔结果");
                $tree[$state['key']] = $statePage;
            }
        } else {
            $tree = $callable($this, null);
            if (!is_bool($tree)) return $this->error("YunjTable [tree] 需返回布尔结果");
        }
        $this->tree = $tree;
        return $this;
    }

    /**
     * Notes: 表头
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:03
     * @param callable|array<col-key,col-args> $cols
     * @return YunjTable
     */
    public function cols($cols) {
        if ($this->existError()) return $this;
        if (!is_callable($cols)) {
            $callable = function () use ($cols) {
                return $cols;
            };
        } else {
            $callable = $cols;
        }
        $this->setOptionCallbale('cols', $callable);
        return $this;
    }

    /**
     * Notes: 执行表头回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjTable
     */
    protected function exec_cols_callable(callable $callable) {
        if ($this->existError()) return $this;
        $states = $this->state;
        $cols = $this->cols ?: [];
        if ($states) {
            foreach ($states as $state) {
                $stateCols = $callable($this, $state['key']);
                if (!is_array($stateCols)) return $this->error("YunjTable [cols] 需返回有效数组");
                $cols[$state['key']] = $cols[$state['key']] ?? [];
                foreach ($stateCols as $key => $args) {
                    list($error, $args) = YunjControl::tableCol($key, $args);
                    if ($error) return $this->error($error);
                    if ($args) $cols[$state['key']][$key] = $args;
                }
            }
        } else {
            $_cols = $callable($this, null);
            if (!is_array($_cols)) return $this->error("YunjTable [cols] 需返回有效数组");
            foreach ($_cols as $key => $args) {
                list($error, $args) = YunjControl::tableCol($key, $args);
                if ($error) return $this->error($error);
                if ($args) $cols[$key] = $args;
            }
        }
        $this->cols = $cols;
        return $this;
    }

    /**
     * Notes: 数量获取
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjTable
     */
    public function count(callable $callable) {
        if ($this->existError()) return $this;
        $this->countCallback = $callable;
        return $this;
    }

    /**
     * Notes: 数据获取
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjTable
     */
    public function items(callable $callable) {
        if ($this->existError()) return $this;
        $this->itemsCallback = $callable;
        return $this;
    }

    /**
     * Notes: 事件处理
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjTable
     */
    public function event(callable $callable) {
        if ($this->existError()) return $this;
        $this->eventCallback = $callable;
        return $this;
    }

    /**
     * @return array
     * @throws GeneralException
     */
    public function viewArgs(): array {
        $args = parent::viewArgs();
        if ($this->state) {
            $args['state'] = $this->state;
        }
        if ($this->filter) {
            $args['filter'] = $this->filter;
        }
        if ($cols = $this->getCols()) {
            $args['cols'] = $cols;
            if ($this->page || is_bool($this->page)) {
                $page = $this->page;
                $args['page'] = $page;
                if ($page) {
                    if ($this->limit) $args['limit'] = $this->limit;
                    if ($this->limits) $args['limits'] = $this->limits;
                }
            }
            if ($this->pk) {
                $args['pk'] = $this->pk;
            }
            if ($this->toolbar) {
                $args['toolbar'] = $this->toolbar;
            }
            if ($this->defaultToolbar) {
                $args['defaultToolbar'] = $this->defaultToolbar;
            }
            if ($this->tree) {
                $args['tree'] = $this->tree;
            }
        }
        return $args;
    }

    /**
     * 获取表格表头
     * @return array|col-args[]|col-args[][]
     */
    public function getCols(): array {
        if (!is_null($this->cols)) {
            return $this->cols;
        }
        $this->execOptionsCallbale("state,cols");
        if ($this->existError()) throw_error_json($this->getError());
        return $this->cols ?? [];
    }

    /**
     * 获取主键配置
     * @param mixed $state
     * @return string
     */
    public function getPk($state): string {
        if ($this->existError()) throw_error_json($this->getError());
        $this->execOptionsCallbale("state,pk");
        if (is_string($this->pk)) {
            $key = $this->pk;
        } else {
            $key = $this->pk[$state] ?? '';
        }
        return $key ? $key : 'id';
    }

    /**
     * 获取主键集合的key值
     * @param mixed $state
     * @return string
     */
    public function getPksKey($state): string {
        return $this->getPk($state) . 's';
    }

    /**
     * Notes: 异步处理状态数量
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:37
     * @return array|Json
     */
    protected function async_stateCount() {
        if (!$this->countCallback) return error_json("YunjTable [count] 未设置");
        $response = [];
        $this->execOptionsCallbale('state,filter');
        if (!$this->countCallback) return error_json("YunjTable [count] 未设置");
        $states = $this->state;
        if (!$states) return error_json();
        $filter = $this->filter;
        foreach ($states as $state) {
            $pksKey = $this->getPksKey($state['key']);
            $data = [
                "state" => $state['key'],
                $pksKey => [],
            ];
            if (isset($filter[$state['key']]) && $filter[$state['key']])
                foreach ($filter[$state['key']] as $k => $field) $data[$k] = "";
            $res = call_user_func_array($this->countCallback, [$this, $data]);
            if (!is_nonnegative_int($res)) {
                $error = is_string($res) ? $res : 'YunjTable [count] 闭包返回数据异常';
                return error_json($error);
            }
            $response[$state['key']] = $res;
        }
        return $response;
    }

    /**
     * Notes: 异步处理数量
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:39
     * @param $data
     * @return mixed
     */
    protected function async_count(array $data) {
        if (!$this->countCallback) return error_json("YunjTable [count] 未设置");
        $filterData = $data['filter'];
        $this->validateCheck($filterData, "Count");
        $res = call_user_func_array($this->countCallback, [$this, $filterData]);
        if (!is_nonnegative_int($res)) {
            $error = is_string($res) ? $res : 'YunjTable [count] 闭包返回数据异常';
            return error_json($error);
        }
        $response = ['count' => $res];
        return $response;
    }

    /**
     * Notes: 异步处理数据项
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:43
     * @param $data
     * @return mixed
     */
    protected function async_items(array $data) {
        if (!$this->itemsCallback) throw_error_json("YunjTable [items] 未设置");

        $filterData = $data['filter'];
        $this->validateCheck($filterData, "Items");
        $res = $this->getItemsData($data['page'], $data['pageSize'], $filterData, $data['sort']);
        if (!is_array($res)) {
            $error = is_string($res) ? $res : 'YunjTable [items] 闭包返回数据异常';
            return error_json($error);
        }

        $response = ['items' => $res];
        return $response;
    }

    /**
     * Notes: 异步处理触发事件
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:43
     * @param $data
     * @return mixed
     */
    protected function async_event(array $data) {
        if (!$this->eventCallback) throw_error_json("YunjTable [event] 未设置");
        $filterData = $data['filter'];
        $filterData['event'] = $data[BuilderConst::ASYNC_EVENT_KEY] ?? '';
        $this->validateCheck($filterData, "Event");
        $pksKey = $this->getPksKey($filterData['state'] ?? null);
        // 触发表格构建器异步事件触发前事件
        event('YunjTableEventBefore', ['builder' => $this, 'event' => $filterData['event'], $pksKey => $filterData[$pksKey]]);
        $response = call_user_func_array($this->eventCallback, [$this, $filterData['event'], $filterData[$pksKey]]);
        return $response;
    }

    /**
     * Notes: 异步处理数据导出
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:43
     * @param $data
     * @return mixed
     */
    protected function async_export(array $data) {
        if (is_mobile()) return error_json('抱歉！暂不支持移动端导出数据');
        if (!$this->itemsCallback) throw_error_json("YunjTable [items] 未设置");
        $this->execOptionsCallbale("state,page,items");
        $filterData = $data['filter'];
        $this->validateCheck($filterData, "Export");

        $res = $this->getItemsData($data['num'], $data['limit'], $filterData, $data['sort']);
        if (!is_array($res)) {
            $error = is_string($res) ? $res : 'YunjTable [items] 闭包返回数据异常';
            return error_json($error);
        }

        $isPage = true;
        if (is_bool($this->page)) {
            $isPage = $this->page;
        } else {
            $isPage = !!$this->page[$filterData['state']];
        }

        $response = [
            'isPage' => !!$isPage,
            'num' => $data['num'],
            'limit' => $data['limit'],
            'items' => $res
        ];
        return $response;
    }

    /**
     * 获取当前分页数据
     * @param int $page
     * @param int $pageSize
     * @param array $filterData
     * @param array $sort
     */
    private function getItemsData(int $page, int $pageSize, array $filterData, array $sort) {
        if (!$this->itemsCallback) throw_error_json("YunjTable [items] 未设置");
        $res = call_user_func_array($this->itemsCallback, [$this, $page, $pageSize, $filterData, $sort]);
        if (!is_array($res) || !$res) {
            return $res;
        }
        $cols = $this->getCols();
        $state = null;
        if ($this->isSetState()) {
            $state = $filterData['state'];
            $cols = $cols[$state] ?? [];
        }
        $pk = $this->getPk($state ?? null);
        $treeParentKey = 'p' . $pk;
        $isSetTree = $this->isSetTree($state);
        // 过滤掉无权限的表头数据。主键、树形pid除外
        $items = [];
        foreach ($res as $v) {
            $item = [
                $pk => $v[$pk] ?? null,
            ];
            if ($isSetTree) {
                $item[$treeParentKey] = $v[$treeParentKey] ?? null;
            }
            foreach ($cols as $k => $col) {
                if (array_key_exists($k, $v)) {
                    $item[$k] = $v[$k];
                }
            }
            if ($item) $items[] = $item;
        }
        return $items;
    }

    /**
     * 验证器校验数据
     * @param array $data 待验证数据
     * @param string $scene
     */
    private function validateCheck(array &$data, string $scene): void {
        if (!$data) return;
        // 事件触发
        if ($scene == 'Event') {
            $pksKey = $this->getPksKey($data['state'] ?? null);
            $rule = [
                'event' => 'require',
                $pksKey => 'array',     // 某些异步事件不需要勾选主键
            ];
            $field = [
                'event' => '事件',
                $pksKey => '执行数据',
            ];
            parent::validateCheckByNormal($rule, $field, $data, $scene);
            return;
        }
        // 其他场景
        $this->execOptionsCallbale('state,filter');
        if ($this->existError()) throw_error_json($this->getError());
        // state
        if ($this->isSetState()) {
            if (!isset($data["state"])) throw_error_json("filter[state]参数缺失");
            if (!in_array($data['state'], array_column($this->state, 'key'))) throw_error_json("filter[state]参数错误");
        }

        // filter
        if ($this->isSetFilter()) {
            $filter = $this->filter;
            if ($this->isSetState()) {
                $state = $data["state"];
                if (!isset($filter[$state]) || !$filter[$state]) return;
                $fields = $filter[$state];
            } else {
                $fields = $filter;
            }
            parent::validateCheckByFieldsData($fields, $data, $scene);
        }
    }

}